#!/bin/bash

set -e
DEBUG="${DEBUG:-}"
readonly -- ME="$(basename "$0")"
readonly -a MASKS=( 
	0
	2147483648 3221225472 3758096384 4026531840
	4160749568 4227858432 4261412864 4278190080
	4286578688 4290772992 4292870144 4293918720
	4294443008 4294705152 4294836224 4294901760
	4294934528 4294950912 4294959104 4294963200
	4294965248 4294966272 4294966784 4294967040
	4294967168 4294967232 4294967264 4294967280
	4294967288 4294967292 4294967294 4294967295
	-1
)

function Usage () {
	echo "Usage: $ME <string> <network>"
	echo "<string> may include these special character sequences:"
	echo "  %b -- broadcast address (e.g. 192.168.1.255)"
	echo "  %f -- first valid address in the network (e.g. 192.168.1.1)"
	echo "  %h -- host address (e.g. 192.168.1.100)"
	echo "  %l -- last valid address in the network (e.g. 192.168.1.254)"
	echo "  %m -- netmask (e.g. 255.255.255.0)"
	echo "  %n -- network address (e.g. 192.168.1.0)"
	echo "  %p -- netmask prefix (e.g. 24 as in 192.168.1.0/24)"
	echo "  %% -- the literal '%' character"
	echo "<network> may be specified in one of two forms:"
	echo "  1. one argument -- any address, belonging to the network, slash prefix"
	echo "     (e.g. 192.168.1.100/24 or 10.0.0.0/8 or even 192.168.14.20/32)"
	echo "  2. two arguments -- first: any address, belonging to the network; second: netmask as dotted quad"
	echo "     (e.g. 192.168.1.100 255.255.255.0 or 10.0.0.0 255.255.255.255)"
	echo "  If there is no way to determine netmask, the warning is issued and the mask 255.255.255.255 is assumed."
	echo "Complex example (first the command, then its result):"
	echo "\$ $ME 'host %h belongs to network %n/%p' 192.168.1.100 255.255.255.0"
	echo "host 192.168.1.100 belongs to network 192.168.1.0/24"
	exit 0
}
function ErrOut () { echo "$@" >& 2;                              }
function Debug ()  { [[ "$DEBUG" ]] && ErrOut "[D] $@"; return 0; }
function Warn ()   {                   ErrOut "[W] $@";           }
function Fail ()   {                   ErrOut "[F] $@"; exit 1;   }
function Quad2Dec () {
	if [[ "$1" ]]; then
		local -i Q1 Q2 Q3 Q4
		IFS="." read Q1 Q2 Q3 Q4 <<< "$1"
		if (( Q1 >= 0 && Q1 < 256 && Q2 >= 0 && Q2 < 256 && Q3 >= 0 && Q3 < 256 && Q4 >= 0 && Q4 < 256 )); then
			echo "$(( (Q1 << 24) + (Q2 << 16) + (Q3 << 8) + Q4 ))"
		else
			Warn "Quad2Dec: not a valid IP dotted quad: [$1]."
			return 1
		fi
	else
		Debug "Quad2Dec: no argument given."
	fi
}
function Dec2Quad () {
	if [[ "$1" ]]; then
		local -i Q1 Q2 Q3 Q4 DEC="${1:-0}"
		if (( DEC >= 0 && DEC < 4294967296 )); then
			let "Q1 = (DEC >> 24 << 24)"                || true
			let "Q2 = (DEC >> 16 << 16) - Q1"           || true
			let "Q3 = (DEC >> 8  << 8 ) - Q1 - Q2"      || true
			let "Q4 = (DEC            ) - Q1 - Q2 - Q3" || true
			let "Q1 = Q1 >> 24" || true
			let "Q2 = Q2 >> 16" || true
			let "Q3 = Q3 >> 8"  || true
			echo "$Q1.$Q2.$Q3.$Q4"
		else
			Warn "Dec2Quad: not a valid IP: [$1]."
			return 1
		fi
	else
		Debug "Dec2Quad: no argument given."
	fi
}

[[ "$1" ]] || Usage
declare -i BITS ADDRESS="$(Quad2Dec "$1")"

if [[ "$2" ]]; then
	declare -i DEC MASK="$(Quad2Dec "$2")"
	for BITS in ${!MASKS[@]}; do
		DEC=${MASKS[$BITS]}
		(( MASK == DEC )) && break
	done
	(( DEC < 0 )) && Fail "Main: netmask [$2] seems to be invalid."
	NETADDR=$(Dec2Quad "$(( ADDRESS & MASK ))")
else
	NETADDR=$(Dec2Quad "$ADDRESS")
	BITS=32
fi

echo "$NETADDR/$BITS"
exit 0